﻿/*******************************************************************************
 *    Project  : AwesomiumSharp
 *    File     : WebCore.cs
 *    Version  : 1.6.2.0 
 *    Date     : 08/02/2011
 *    Editor   : Perikles C. Stephanidis (AmaDeuS)
 *    Contact  : perikles@stephanidis.net
 *-------------------------------------------------------------------------------
 *
 *    Notes    :
 *    
 *    The WebCore is the "core" of Awesomium; it manages the lifetime of all views
 *    (see <see cref="WebView"/> and <see cref="Windows.Controls.WebControl"/>) 
 *    and maintains useful services like resource caching and 
 *    network connections.
 *    
 *    Changelog: 
 *    
 *    https://github.com/khrona/AwesomiumSharp/commits/master.atom
 *    
 ********************************************************************************/

#region Using
using System;
using System.Text;
using System.Threading;
using System.Collections.Generic;
using System.Runtime.InteropServices;
#if !USING_MONO
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
#endif
#endregion

#if USING_MONO
namespace AwesomiumMono
#else
namespace AwesomiumSharp
#endif
{

    #region Enums
    public enum LogLevel
    {
        None,
        Normal,
        Verbose
    };

    public enum MouseButton
    {
        Left,
        Middle,
        Right
    };

    public enum URLFilteringMode
    {
        None,
        Blacklist,
        Whitelist
    };

    public enum WebKeyType
    {
        KeyDown,
        KeyUp,
        Char
    };

    public enum WebKeyModifiers
    {
        /// <summary>
        /// Whether or not a Shift key is down.
        /// </summary>
        ShiftKey = 1 << 0,
        /// <summary>
        /// Whether or not a Control key is down.
        /// </summary>
        ControlKey = 1 << 1,
        /// <summary>
        /// Whether or not an ALT key is down.
        /// </summary>
        AltKey = 1 << 2,
        /// <summary>
        /// Whether or not a meta key (Command-key on Mac, Windows-key on Windows) is down.
        /// </summary>
        MetaKey = 1 << 3,
        /// <summary>
        /// Whether or not the key pressed is on the keypad.
        /// </summary>
        IsKeypad = 1 << 4,
        /// <summary>
        /// Whether or not the character input is the result of an auto-repeat timer.
        /// </summary>
        IsAutoRepeat = 1 << 5,
    };

    public enum CursorType
    {
        Pointer,
        Cross,
        Hand,
        IBeam,
        Wait,
        Help,
        EastResize,
        NorthResize,
        NortheastResize,
        NorthwestResize,
        SouthResize,
        SoutheastResize,
        SouthwestResize,
        WestResize,
        NorthSouthResize,
        EastWestResize,
        NortheastSouthwestResize,
        NorthwestSoutheastResize,
        ColumnResize,
        RowResize,
        MiddlePanning,
        EastPanning,
        NorthPanning,
        NortheastPanning,
        NorthwestPanning,
        SouthPanning,
        SoutheastPanning,
        SouthwestPanning,
        WestPanning,
        Move,
        VerticalText,
        Cell,
        ContextMenu,
        Alias,
        Progress,
        NoDrop,
        Copy,
        None,
        NotAllowed,
        ZoomIn,
        ZoomOut,
        Custom
    };

    public enum IMEState
    {
        Disable = 0,
        MoveWindow = 1,
        CompleteComposition = 2
    };
    #endregion

    #region Structs
    /// <summary>
    /// Represents a generic keyboard event that can be created from a platform-specific event or 
    /// synthesized from a virtual event. Used by <see cref="WebView.InjectKeyboardEvent"/> and
    /// <see cref="Windows.Controls.WebControl.InjectKeyboardEvent"/>.
    /// </summary>
    [StructLayout( LayoutKind.Sequential )]
    public struct WebKeyboardEvent
    {
        /// <summary>
        /// The type of this <see cref="WebKeyboardEvent"/>.
        /// </summary>
        public WebKeyType Type;
        /// <summary>
        /// The current state of the keyboard. Modifiers may be OR'd together to represent multiple values.
        /// </summary>
        public WebKeyModifiers Modifiers;
        /// <summary>
        /// The virtual key-code associated with this keyboard event. 
        /// This is either directly from the event (ie, WPARAM on Windows) or via a mapping function.
        /// </summary>
        public VirtualKey VirtualKeyCode;
        /// <summary>
        /// The actual key-code generated by the platform. The DOM specification primarily uses 
        /// Windows-equivalent codes (hence virtualKeyCode above) but it helps to additionally 
        /// specify the platform-specific key-code as well.
        /// </summary>
        public int NativeKeyCode;
        /// <summary>
        /// The actual text generated by this keyboard event. 
        /// This is usually only a single character but we're generous and cap it at a max of 4 characters.
        /// </summary>
        [MarshalAs( UnmanagedType.ByValArray, SizeConst = 4 )]
        public ushort[] Text;
        /// <summary>
        /// The text generated by this keyboard event before all modifiers except shift are applied. 
        /// This is used internally for working out shortcut keys. 
        /// This is usually only a single character but we're generous and cap it at a max of 4 characters.
        /// </summary>
        [MarshalAs( UnmanagedType.ByValArray, SizeConst = 4 )]
        public ushort[] UnmodifiedText;
        /// <summary>
        /// Whether or not the pressed key is a "system key". 
        /// This is a Windows-only concept and should be "false" for all non-Windows platforms. 
        /// For more information, see the following link: http://msdn.microsoft.com/en-us/library/ms646286.aspx
        /// </summary>
        public bool IsSystemKey;
    };

    /// <summary>
    /// A simple rectangle class. Used with <see cref="WebView.DirtyBounds"/>, <see cref="Windows.Controls.WebControl.DirtyBounds"/>
    /// and various <see cref="RenderBuffer"/> methods.
    /// </summary>
    [StructLayout( LayoutKind.Sequential )]
    public struct AweRect
    {
        public int X;
        public int Y;
        public int Width;
        public int Height;
    };
    #endregion


    #region Documentation
    /// <summary>
    /// The WebCore is the "core" of Awesomium; it manages the lifetime of all views
    /// (see <see cref="WebView"/> and <see cref="Windows.Controls.WebControl"/>) and maintains useful services
    /// like resource caching and network connections.
    /// </summary>
    /// <remarks>
    /// Generally, you should initialize the WebCore (<see cref="WebCore.Initialize"/>) providing
    /// your custom configuration, before creating any views and shut it down (<see cref="WebCore.Shutdown"/>)
    /// at the end of your program.
    /// <p/>
    /// <note>
    /// If you do not initialize <see cref="WebCore"/>, the core will automatically
    /// start, using default configuration, when you create the first view by either calling
    /// <see cref="WebCore.CreateWebView"/> or by instantiating a <see cref="Windows.Controls.WebControl"/>.
    /// </note>
    /// <p/>
    /// <note type="caution">
    /// Do not call any of the members of this class (other than <see cref="WebCore.Initialize"/>
    /// or <see cref="WebCore.CreateWebView"/>) before starting the core.
    /// </note>
    /// </remarks>
    /// <threadsafety static="false" instance="false" />
    #endregion
    public static class WebCore
    {
        #region Fields
#if DEBUG_AWESOMIUM
        internal const string DLLName = "Awesomium_d";
#elif MONO_MACOSX
        internal const string DLLName = "@executable_path/../Frameworks/Awesomium.framework/Versions/A/Awesomium";
#else
        internal const string DLLName = "Awesomium";
#endif

#if !USING_MONO
        private static List<String> dependencies;
#endif
        private static List<IWebView> activeWebViews;
        private static List<IntPtr> pendingWebViews;
        private static WebCoreConfig configuration;
        private static Timer updateTimer;
        private static int updatePeriod = 20;
        private static bool isRunning;
        private static bool isShuttingDown;
        private static int coreThreadID;
        private static SynchronizationContext syncCtx;
        #endregion


        #region Ctors
        static WebCore()
        {
#if !USING_MONO
            dependencies = new List<String>( new String[] 
                { 
    #if DEBUG_AWESOMIUM
                    "Awesomium_d.dll",
                    "AwesomiumProcess_d.exe"
    #else
                    "Awesomium.dll",
                    "AwesomiumProcess.exe",
    #endif
                    "icudt42.dll",
                    @"\locales\en-US.dll"
                } );
#endif

            pendingWebViews = new List<IntPtr>();
        }
        #endregion


        #region Methods

        #region Internal

        #region VerifyDependencies
#if !USING_MONO
        internal static void VerifyDependencies()
        {
            Assembly asm = Assembly.GetEntryAssembly();

            foreach ( String s in dependencies )
            {
                if ( !File.Exists( String.Format( "{0}{1}{2}", Path.GetDirectoryName( asm.Location ), Path.DirectorySeparatorChar, s ) ) )
                    throw new DllNotFoundException( String.Format( "The dependency: {0} could not be found!", s ) );
            }
        }
#endif
        #endregion

        #region VerifyLive
        internal static void VerifyLive()
        {
            if ( !isRunning )
                throw new InvalidOperationException( "The WebCore is not running. At least one view needs to be created before" );
        }
        #endregion

        #region CheckAccess
        /// <summary>
        /// Checks thread affinity.
        /// </summary>
        /// <returns>
        /// True if called on the correct thread. False otherwise.
        /// </returns>
        internal static bool CheckAccess()
        {
            return Thread.CurrentThread.ManagedThreadId == coreThreadID;
        }
        #endregion

        #region DestroyView
        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private static extern void awe_webview_destroy( IntPtr webview );

        internal static void DestroyView( IWebView view )
        {
            if ( isRunning && ( view.Instance != IntPtr.Zero ) )
            {
                if ( !isUpdating )
                    awe_webview_destroy( view.Instance );
                else
                    pendingWebViews.Add( view.Instance );

                RemoveView( view );
            }
        }
        #endregion

        #region RemoveView
        private static void RemoveView( IWebView view )
        {
            if ( !isShuttingDown )
            {
                if ( ( activeWebViews != null ) && activeWebViews.Contains( view ) )
                    activeWebViews.Remove( view );

                //if ( activeWebViews.Count == 0 )
                //    Shutdown();
            }
        }
        #endregion

        #region StopUpdateTimer
        private static void StopUpdateTimer()
        {
            if ( updateTimer != null )
            {
                updateTimer.Dispose();
                updateTimer = null;
            }
        }
        #endregion

        #endregion

        #region Public

        #region Initialize
        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private extern static void awe_webcore_initialize( bool enable_plugins,
            bool enable_javascript,
            bool enable_databases,
            IntPtr user_data_path,
            IntPtr plugin_path,
            IntPtr log_path,
            LogLevel log_level,
            bool force_single_process,
            IntPtr child_process_path,
            bool enable_auto_detect_encoding,
            IntPtr accept_language_override,
            IntPtr default_charset_override,
            IntPtr user_agent_override,
            IntPtr proxy_server,
            IntPtr proxy_config_script,
            IntPtr auth_server_whitelist,
            bool save_cache_and_cookies,
            int max_cache_size,
            bool disable_same_origin_policy,
            bool disable_win_message_pump,
            IntPtr custom_css );

        /// <summary>
        /// Initializes the <see cref="WebCore"/> singleton with certain configuration settings.
        /// </summary>
        /// <param name="config">
        /// An instance of <see cref="WebCoreConfig"/> specifying configuration settings.
        /// </param>
        /// <param name="start">
        /// True if the <see cref="WebCore"/> should immediately start. False to perform lazy instantiation.
        /// The <see cref="WebCore"/> will start when the first view (<see cref="WebView"/> or <see cref="Windows.Controls.WebControl"/>)
        /// is created. The default is true.
        /// </param>
        /// <remarks>
        /// <para>
        /// If you do not call this method, the <see cref="WebCore"/> will start automatically,
        /// using default configuration settings, when you first create a view through <see cref="CreateWebView"/>
        /// or by instantiating a <see cref="Windows.Controls.WebControl"/>.
        /// </para>
        /// <note>
        /// If you are not sure if <see cref="WebCore"/> is running, check <see cref="IsRunning"/>
        /// before calling this method. If <see cref="WebCore"/> is running, you will have
        /// to shut it down (see <see cref="Shutdown"/>) and <b>restart the hosting application</b> before 
        /// initializing <see cref="WebCore"/> again. Only a single initialization/instantiation of the 
        /// <see cref="WebCore"/> is possible per application session (process).
        /// </note>
        /// <para>
        /// If you set <see cref="WebCoreConfig.SaveCacheAndCookies"/> to true, 
        /// please make sure that your hosting application is a single instance application, 
        /// unless you are sure that you provide a unique <see cref="WebCoreConfig.UserDataPath"/>
        /// for each of your application's instances.
        /// </para>
        /// </remarks>
        /// <exception cref="InvalidOperationException">
        /// The member is called while <see cref="WebCore"/> is running.
        /// </exception>
        /// <seealso cref="IsRunning"/>
        public static void Initialize( WebCoreConfig config, bool start = true )
        {
            if ( isRunning )
                throw new InvalidOperationException( "The WebCore is already initialized. Call Shutdown() before initializing it again." );

            isShuttingDown = false;
            configuration = config;

            if ( start )
                Start();
        }

        private static void Start()
        {
#if !USING_MONO
            VerifyDependencies();
#endif
            if ( !isRunning )
            {
                WebCoreConfig config = configuration ?? new WebCoreConfig { SaveCacheAndCookies = true, EnablePlugins = true };

                StringHelper userDataPathStr = new StringHelper( config.UserDataPath );
                StringHelper pluginPathStr = new StringHelper( config.PluginPath );
                StringHelper logPathStr = new StringHelper( config.LogPath );
                StringHelper childProcessPathStr = new StringHelper( config.ChildProcessPath );
                StringHelper acceptLanguageStr = new StringHelper( config.AcceptLanguageOverride );
                StringHelper defaultCharsetStr = new StringHelper( config.DefaultCharsetOverride );
                StringHelper userAgentOverrideStr = new StringHelper( config.UserAgentOverride );
                StringHelper proxyServerStr = new StringHelper( config.ProxyServer );
                StringHelper proxyConfigScriptStr = new StringHelper( config.ProxyConfigScript );
                StringHelper authServerWhitelistStr = new StringHelper( config.AuthServerWhitelist );
                StringHelper customCSSStr = new StringHelper( config.CustomCSS );

#if !USING_MONO
                EnableThemingInScope activationContext = null;

                if ( config.EnableVisualStyles )
                    activationContext = new EnableThemingInScope();
#endif
                awe_webcore_initialize( config.EnablePlugins,
                    config.EnableJavascript,
                    config.EnableDatabases,
                    userDataPathStr.Value,
                    pluginPathStr.Value,
                    logPathStr.Value,
                    config.LogLevel,
                    config.ForceSingleProcess,
                    childProcessPathStr.Value,
                    config.EnableAutoDetectEncoding,
                    acceptLanguageStr.Value,
                    defaultCharsetStr.Value,
                    userAgentOverrideStr.Value,
                    proxyServerStr.Value,
                    proxyConfigScriptStr.Value,
                    authServerWhitelistStr.Value,
                    config.SaveCacheAndCookies,
                    config.MaxCacheSize,
                    config.DisableSameOriginPolicy,
                    config.DisableWinMessagePump,
                    customCSSStr.Value );

                updatePeriod = config.AutoUpdatePeriod;

#if !USING_MONO
                homeURL = config.HomeURL;

                if ( activationContext != null )
                    activationContext.Dispose();
#endif

                activeWebViews = new List<IWebView>();
                syncCtx = SynchronizationContext.Current;

                // Just to be sure.
                StopUpdateTimer();

                // In some scenarios (like when using Mono) just the
                // SynchronizationContext is not enough to ensure thread affinity.
                coreThreadID = Thread.CurrentThread.ManagedThreadId;

                // We will not start auto-update unless we get a synchronization context.
                if ( syncCtx != null )
                    updateTimer = new Timer( UpdateTimerCallback, null, 20, updatePeriod );

                isRunning = true;
            }
        }
        #endregion

        #region Shutdown
        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private static extern void awe_webcore_shutdown();

        /// <summary>
        /// Destroys the <see cref="WebCore"/> and any lingering <see cref="WebView"/>
        /// and <see cref="Windows.Controls.WebControl"/> instances.
        /// </summary>
        /// <remarks>
        /// Make sure that this is not called while the hosting UI of any views
        /// created by this <see cref="WebCore"/>, is still live and visible. 
        /// This method will destroy all views created by this <see cref="WebCore"/>.
        /// Any attempt to access them or any member of this class (other than <see cref="Initialize"/>
        /// and <see cref="CreateWebView"/>) after calling this method,
        /// may throw a <see cref="InvalidOperationException"/>.
        /// </remarks>
#if !USING_MONO
        [HandleProcessCorruptedStateExceptions]
#endif
        public static void Shutdown()
        {
            if ( isRunning )
            {
                isShuttingDown = true;

                // Stop the update timer.
                StopUpdateTimer();

                // Inform views by closing them.
                foreach ( IWebView i in activeWebViews )
                    i.Close();

                try
                {
                    // We may be attempting to shutdown from
                    // within an event handler, fired during Update.
                    // Let the update complete and queue the shutdown.
                    if ( !isUpdating )
                        awe_webcore_shutdown();
                    else
                        pendingShutdown = true;
                }
                catch { }
                finally
                {
                    activeWebViews.Clear();
                    activeWebViews = null;

                    isRunning = false;
                    isShuttingDown = false;
                }
            }
        }
        #endregion

        #region Update
        private static bool isUpdating;
        private static bool pendingShutdown;

        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private static extern void awe_webcore_update();

        /// <summary>
        /// Updates the <see cref="WebCore"/> and allows it to conduct various operations such
        /// as updating the render buffer of each view, destroying any views that are queued for destruction,
        /// and invoking any queued events (including <see cref="WebView.IsDirtyChanged"/> and 
        /// <see cref="Windows.Controls.WebControl.IsDirtyChanged"/>).
        /// </summary>
        /// <remarks>
        /// If you are using Awesomium from a UI thread (regular use), you never need to call this method.
        /// Internal auto-update takes care of this and you only need to watch for the <see cref="WebView.IsDirtyChanged"/>
        /// or <see cref="Windows.Controls.WebControl.IsDirtyChanged"/> events. If you are using Awesomium from a
        /// non graphical environment (Console application, Service or non-UI thread), auto-update is not available and
        /// you must manually call this method from either your application's message loop or by creating a timer. 
        /// In this case, you must make sure that any calls to any of the classes of this assembly,
        /// are made from the same thread.
        /// <note>
        /// You can check <see cref="IsAutoUpdateEnabled"/> to know if auto-update is already enabled.
        /// </note>
        /// </remarks>
        /// <exception cref="InvalidOperationException">
        /// The member is called before starting <see cref="WebCore"/>.
        /// </exception>
        /// <exception cref="AccessViolationException">
        /// You attempted to access the member from a thread other than
        /// thread where <see cref="WebCore"/> was created.
        /// </exception>
#if !USING_MONO
        [HandleProcessCorruptedStateExceptions]
#endif
        public static void Update()
        {
            VerifyLive();

            isUpdating = true;

            try
            {
                awe_webcore_update();

                // We may have attempted to destroy views from within
                // an event handler fired during awe_webcore_update.
                if ( pendingWebViews.Count > 0 )
                {
                    if ( isRunning )
                    {
                        foreach ( IntPtr instance in pendingWebViews )
                        {
                            awe_webview_destroy( instance );
                        }
                    }

                    pendingWebViews.Clear();
                }

                // We may have attempted to shutdown from within
                // an event handler fired during awe_webcore_update.
                if ( pendingShutdown )
                {
                    pendingShutdown = false;
                    awe_webcore_shutdown();
                }
            }
            catch
            {
                /* TODO: Design an error handling model. 
                 * AccessViolation is the most typical exception for the time being
                 * and it appears in many occasions; not only in wrong thread scenarios */
                Shutdown();
            }
            finally
            {
                isUpdating = false;
            }
        }
        #endregion

        #region SetBaseDirectory
        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private static extern void awe_webcore_set_base_directory( IntPtr base_dir_path );

        /// <summary>
        /// Sets the base directory all of your local assets.
        /// </summary>
        /// <param name="baseDirPath">
        /// The absolute path to your base directory. The base directory is a location that holds all of your local assets.
        /// It will be used with <see cref="WebView.LoadHTML"/>, <see cref="WebView.LoadFile"/>, 
        /// <see cref="Windows.Controls.WebControl.LoadHTML"/> and <see cref="Windows.Controls.WebControl.LoadFile"/>
        /// to resolve relative URLs.
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The member is called before starting <see cref="WebCore"/>.
        /// </exception>
        public static void SetBaseDirectory( string baseDirPath )
        {
            VerifyLive();

            StringHelper baseDirPathStr = new StringHelper( baseDirPath );
            awe_webcore_set_base_directory( baseDirPathStr.Value );
        }
        #endregion

        #region CreateWebView
        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private static extern IntPtr awe_webcore_create_webview( int width, int height, bool viewSource );

        /// <summary>
        /// Create a <see cref="WebView"/> (think of it like a tab in Chrome, you can load web-pages
        /// into it, interact with it, and render it to a buffer).
        /// </summary>
        /// <param name="width">The initial width of the view in pixels.</param>
        /// <param name="height">The initial height of the view in pixels.</param>
        /// <param name="viewSource">
        /// Enable View-Source mode on this <see cref="WebView"/> to view 
        /// the HTML source of any web-page (must be loaded via <see cref="WebView.LoadURL"/>).
        /// </param>
        /// <returns>
        /// A new <see cref="WebView"/> instance.
        /// </returns>
        /// <remarks>
        /// If you call this method before initializing the <see cref="WebCore"/>, Awesomium
        /// will automatically start with default configuration settings.
        /// </remarks>
        public static WebView CreateWebView( int width, int height, bool viewSource = false )
        {
            if ( !isRunning )
                Start();

            IntPtr webviewPtr = awe_webcore_create_webview( width, height, viewSource );
            WebView view = new WebView( webviewPtr );
            activeWebViews.Add( view );

            return view;
        }

        // Used by WebControl.
        internal static IntPtr CreateWebViewInstance( int width, int height, IWebView host, bool viewSource = false )
        {
            if ( !isRunning )
                Start();

            IntPtr webviewPtr = awe_webcore_create_webview( width, height, viewSource );
            activeWebViews.Add( host );
            return webviewPtr;
        }
        #endregion

        #region SetCustomResponsePage
        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private static extern void awe_webcore_set_custom_response_page( int status_code, IntPtr file_path );

        /// <summary>
        /// Sets a custom response page to use when a WebView encounters a certain HTML status code from the server (like '404 - File not found').
        /// </summary>
        /// <param name="statusCode">
        /// The status code this response page should be associated with. See http://en.wikipedia.org/wiki/List_of_HTTP_status_codes.
        /// </param>
        /// <param name="filePath">
        /// The local page to load as a response, should be a path relative to the base directory.
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The member is called before starting <see cref="WebCore"/>.
        /// </exception>
        public static void SetCustomResponsePage( int statusCode, string filePath )
        {
            VerifyLive();

            StringHelper filePathStr = new StringHelper( filePath );
            awe_webcore_set_custom_response_page( statusCode, filePathStr.Value );
        }
        #endregion

        #region ClearCache
        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private static extern void awe_webcore_clear_cache();

        /// <summary>
        /// Clears the disk cache and media cache.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// The member is called before starting <see cref="WebCore"/>.
        /// </exception>
        public static void ClearCache()
        {
            VerifyLive();
            awe_webcore_clear_cache();
        }
        #endregion

        #region ClearCookies
        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private static extern void awe_webcore_clear_cookies();

        /// <summary>
        /// Clears all stored cookies.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// The member is called before starting <see cref="WebCore"/>.
        /// </exception>
        public static void ClearCookies()
        {
            VerifyLive();
            awe_webcore_clear_cookies();
        }
        #endregion

        #region SetCookie
        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private static extern void awe_webcore_set_cookie( IntPtr url,
                                       IntPtr cookie_string,
                                       bool is_http_only,
                                       bool force_session_cookie );

        /// <summary>
        /// Sets a cookie for a certain URL.
        /// </summary>
        /// <param name="url">
        /// The URL to set the cookie on.
        /// </param>
        /// <param name="cookieString">
        /// The cookie string, for example:
        /// <c>
        /// "key1=value1; key2=value2"
        /// </c>
        /// </param>
        /// <param name="isHttpOnly">
        /// Whether or not this cookie is HTTP-only.
        /// </param>
        /// <param name="forceSessionCookie">
        /// Whether or not to force this as a session cookie.
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The member is called before starting <see cref="WebCore"/>.
        /// </exception>
        public static void SetCookie( string url, string cookieString, bool isHttpOnly = false, bool forceSessionCookie = false )
        {
            VerifyLive();

            StringHelper urlStr = new StringHelper( url );
            StringHelper cookieStringStr = new StringHelper( cookieString );

            awe_webcore_set_cookie( urlStr.Value, cookieStringStr.Value, isHttpOnly, forceSessionCookie );
        }
        #endregion

        #region GetCookies
        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private static extern IntPtr awe_webcore_get_cookies( IntPtr url, bool exclude_http_only );

        /// <summary>
        /// Gets all cookies for a certain URL.
        /// </summary>
        /// <param name="url">
        /// The URL whose cookies will be retrieved.
        /// </param>
        /// <param name="excludeHttpOnly">
        /// Whether or not to exclude HTTP-only cookies from the result.
        /// </param>
        /// <returns>
        /// A <see cref="String"/> representing the cookie.
        /// </returns>
        /// <exception cref="InvalidOperationException">
        /// The member is called before starting <see cref="WebCore"/>.
        /// </exception>
        public static String GetCookies( string url, bool excludeHttpOnly = true )
        {
            VerifyLive();

            StringHelper urlStr = new StringHelper( url );
            IntPtr temp = awe_webcore_get_cookies( urlStr.Value, excludeHttpOnly );

            return StringHelper.ConvertAweString( temp );
        }
        #endregion

        #region DeleteCookie
        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private static extern void awe_webcore_delete_cookie( IntPtr url, IntPtr cookie_name );

        /// <summary>
        /// Deletes a certain cookie on a certain URL.
        /// </summary>
        /// <param name="url">
        /// The URL that we will be deleting cookies on.
        /// </param>
        /// <param name="cookieName">
        /// The name of the cookie that will be deleted.
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The member is called before starting <see cref="WebCore"/>.
        /// </exception>
        public static void DeleteCookie( string url, string cookieName )
        {
            VerifyLive();

            StringHelper urlStr = new StringHelper( url );
            StringHelper cookieNameStr = new StringHelper( cookieName );

            awe_webcore_delete_cookie( urlStr.Value, cookieNameStr.Value );
        }
        #endregion

        #region SuppressPrinterDialog
        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private static extern void awe_webcore_set_suppress_printer_dialog( bool suppress );

        /// <summary>
        /// Set whether or not the printer dialog should be suppressed or not.
        /// Set this to true to hide printer dialogs and print immediately
        /// using the OS's default printer when <see cref="WebView.Print"/> or
        /// <see cref="Windows.Controls.WebControl.Print"/> is called.
        /// The default is false is you never call this.
        /// </summary>
        /// <param name="suppress">
        /// True to suppress the dialog. False otherwise.
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The member is called before starting <see cref="WebCore"/>.
        /// </exception>
        public static void SuppressPrinterDialog( bool suppress )
        {
            VerifyLive();
            awe_webcore_set_suppress_printer_dialog( suppress );
        }
        #endregion

        #endregion

        #endregion

        #region Properties

        #region SynchronizationContext
        internal static SynchronizationContext SynchronizationContext
        {
            get
            {
                if ( syncCtx == null )
                    syncCtx = SynchronizationContext.Current;

                return syncCtx;
            }
        }
        #endregion

        #region PluginsEnabled
        [return: MarshalAs( UnmanagedType.I1 )]
        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private static extern bool awe_webcore_are_plugins_enabled();

        /// <summary>
        /// Gets if plugins are enabled.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// The member is accessed before starting <see cref="WebCore"/>.
        /// </exception>
        public static bool PluginsEnabled
        {
            get
            {
                VerifyLive();
                return awe_webcore_are_plugins_enabled();
            }
        }
        #endregion

        #region BaseDirectory
        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private static extern IntPtr awe_webcore_get_base_directory();

        /// <summary>
        /// Gets or sets the base directory (used with <see cref="WebView.LoadHTML"/>)
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// The member is accessed before starting <see cref="WebCore"/>.
        /// </exception>
        public static string BaseDirectory
        {
            get
            {
                VerifyLive();
                return StringHelper.ConvertAweString( awe_webcore_get_base_directory() );
            }
            set
            {
                VerifyLive();
                StringHelper baseDirPathStr = new StringHelper( value );
                awe_webcore_set_base_directory( baseDirPathStr.Value );
            }
        }
        #endregion

        #region IsRunning
        /// <summary>
        /// Gets if the <see cref="WebCore"/> is currently running.
        /// </summary>
        /// <seealso cref="Initialize"/>
        public static bool IsRunning
        {
            get
            {
                return isRunning;
            }
        }
        #endregion

        #region IsShuttingDown
        /// <summary>
        /// Gets if the WebCore is currently shutting down.
        /// </summary>
        public static bool IsShuttingDown
        {
            get
            {
                return isShuttingDown;
            }
        }
        #endregion

        #region IsAutoUpdateEnabled
        /// <summary>
        /// Gets if automatic update is successfully enabled.
        /// </summary>
        /// <seealso cref="Update"/>
        public static bool IsAutoUpdateEnabled
        {
            get
            {
                return ( updateTimer != null );
            }
        }
        #endregion

        #region AutoUpdatePeriod
        /// <summary>
        /// Gets or sets the time interval between invocations of <see cref="WebCore.Update"/>, in milliseconds.
        /// The default is 20.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// Attempted to set this before starting <see cref="WebCore"/>.
        /// </exception>
        public static int AutoUpdatePeriod
        {
            get
            {
                return updatePeriod;
            }
            set
            {
                VerifyLive();

                if ( updatePeriod == value )
                    return;

                updatePeriod = value;

                if ( updateTimer != null )
                    updateTimer.Change( 0, value );
            }
        }
        #endregion

        #region HomeURL
#if !USING_MONO
        private static string homeURL = "about:blank";

        /// <summary>
        /// Gets or sets the URL that will be used as the Home URL
        /// for <see cref="Windows.Controls.WebControl"/>s.
        /// </summary>
        /// <remarks>
        /// This setting is used by <see cref="Windows.Controls.WebControl"/>s to automatically
        /// handle the <see cref="System.Windows.Input.NavigationCommands.BrowseHome"/> command.
        /// The default is: "about:blank".
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        /// A null reference or an empty string defined.
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// Attempted to set this before starting <see cref="WebCore"/>.
        /// </exception>
        public static string HomeURL
        {
            get
            {
                return homeURL;
            }
            set
            {
                if ( String.Compare( homeURL, value, false ) == 0 )
                    return;

                if ( String.IsNullOrWhiteSpace( value ) )
                    throw new ArgumentNullException();

                VerifyLive();

                homeURL = value;
            }
        }
#endif
        #endregion

        #endregion

        #region Event Handlers
        [return: MarshalAs( UnmanagedType.I1 )]
        [DllImport( WebCore.DLLName, CallingConvention = CallingConvention.Cdecl )]
        private static extern bool awe_webview_is_dirty( IntPtr webview );

        private static void UpdateTimerCallback( object state )
        {
            // Seems strange? It's not. We check the variable faster
            // then we get a value from a property. The logical OR ensures
            // that if the first part succeeds, the second won't be called at all.
            // But in the rare case we have reached here without a valid Synchronization
            // Context, the property's getter will attempt to aquire one now;
            // if this fails too, we can stop the timer.
            if ( syncCtx != null || SynchronizationContext != null )
            {
                // Wait for the previous update to complete before we post another.
                if ( !isUpdating )
                    // API calls should normally be thread safe but they are not.
                    // We need the synchronization context to marshal calls.
                    syncCtx.Post( UpdateSync, state );
            }
            else
            {
                // We should not be here anyway!
                StopUpdateTimer();
            }
        }

        private static void UpdateSync( object state )
        {
            // That's an additional check to ensure
            // thread affinity, needed by Awesomium.
            if ( !CheckAccess() )
            {
                StopUpdateTimer();
                return;
            }

            // Prevent race condition. We use Post, not Send.
            if ( !isRunning || isShuttingDown || isUpdating )
                return;

            Update();

            if ( activeWebViews != null )
            {
                foreach ( IWebView view in activeWebViews )
                {
                    if ( view.Instance != IntPtr.Zero )
                        view.IsDirty = awe_webview_is_dirty( view.Instance );
                }
            }
        }
        #endregion
    }
}
